package com.ge.pp;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JOptionPane;

public class PPServer {
	private ServerSocket mainSocket;
	private String dbPath;
	private PPDatabase db;
	private boolean quitFlag;
	private Thread serverThread;
	private Vector<ClientHandler> clientList;
	private Hashtable<String, Thread> clientMap;
	private HashMap<String, Vector<Case>> referralMap;
	
	public void AddClient(String id, Thread handler) {
		clientMap.put(id, handler);
	}
	public void RemoveClient(String id) {
		clientMap.remove(id);
	}
	public synchronized void AddReferral(String id, Case c) {
		if (referralMap.containsKey(id)) {
			Vector<Case> cases = referralMap.get(id);
			cases.add(c);
		} else {
			Vector<Case> cases = new Vector<Case>();
			cases.add(c);
			referralMap.put(id, cases);
		}
	}
	
	public synchronized void RemoveReferral(String docID, int id) {
		Vector<Case> cases = null;
		if (referralMap.containsKey(docID)) {
			cases = referralMap.get(docID);			
			cases.remove(id);		
						
			if(cases.size() == 0) 
				referralMap.remove(docID);
		}
	}
	public synchronized Vector<Case> GetReferrals(String docID) {
		Vector<Case> cases = new Vector<Case>();
		if (referralMap.containsKey(docID)) {
			cases = referralMap.get(docID);
		}
		return cases;
	}
	
	public class ClientHandler extends Thread {

		private Socket client;
		PPDatabase db;
		boolean disconnect = false;
		boolean quitFlag = false;
		
		public ClientHandler(Socket _client, PPDatabase _db) {		
			client = _client;
			db = _db;
		}
		
		public void quit() {
			quitFlag = true;
		}
		
		@Override
		public void run() {
			int caseID;
			String userID=null;
			
			try {
				OutputStream sos = client.getOutputStream();
				DataOutputStream out = new DataOutputStream(sos);		
				
				InputStream sis = client.getInputStream();
				DataInputStream dis = new DataInputStream(sis);
										
				while(!quitFlag && !disconnect) {
					try {
						int msgType = dis.readInt();
						
						switch (msgType) {
							case MessageTypes.QUIT:
								disconnect = true;
								break;
							case MessageTypes.HELLO:
								int msgLen = dis.readInt();					
								byte [] bmsg = new byte[msgLen];
								dis.readFully(bmsg, 0, msgLen);
								String msg = new String(bmsg);
								String[] fields = msg.split(":");
								userID= fields[1];
								if (clientMap.containsKey(userID))
								{
									JOptionPane.showMessageDialog(null, "User already Logged in.", "Already Logged!!", JOptionPane.ERROR_MESSAGE, null);
								}
								else
								{
									System.out.println("Viewer Client: "+userID+" connected.");
									AddClient(userID, this);
								}
								break;
							case MessageTypes.GET_CASES:
								out.writeInt(MessageTypes.CASE_LIST);
								Vector<Integer> caseIDList = db.GetAvailableCaseIDs();
								
								out.writeInt(caseIDList.size());
								for(int caseid:caseIDList) {
									Case c = db.GetCaseByID(caseid);
									out.writeInt(c.id);
									out.writeInt(c.patientid);
									out.writeInt(c.name.length());
									out.writeBytes(c.name);
								}					
								break;
							case MessageTypes.CHECKOUT_CASE:
								caseID = dis.readInt();
								db.CheckoutCase(caseID);
								break;
							case MessageTypes.CHECKIN_CASE:
								caseID = dis.readInt();
								db.CheckinCase(caseID);
								break;
							case MessageTypes.GET_IMAGEINFO_FOR_CASE:
								caseID = dis.readInt();								
								// Look up the case ID, return a list of pictures
								Vector<Integer> imageIDs = db.GetImageIDsByCaseID(caseID);
								
								out.writeInt(MessageTypes.IMAGE_LIST);
								out.writeInt(imageIDs.size());
								for(int i:imageIDs) {
									out.writeInt(i);
									String desc = db.GetImageDesc(i);
									out.writeInt(desc.length());
									out.writeBytes(desc);
								}
								break;
							case MessageTypes.GET_IMAGE_DATA:
								int imageID = dis.readInt();
								String path = db.GetImagePath(imageID);
								PPImage img = new PPImage(imageID, "", path);
								img.LoadImageData();
								out.writeInt(MessageTypes.IMAGE_DATA);
								out.writeInt(img.data.length);
								out.write(img.data);
								break;
							case MessageTypes.GET_PATIENT:
								int patientID = dis.readInt();
								Patient patient = db.GetPatientByID(patientID);
								out.writeInt(MessageTypes.PATIENT_DATA);
								out.writeInt(patient.id);
								out.writeInt(patient.firstname.length());
								out.writeBytes(patient.firstname);
								out.writeInt(patient.lastname.length());
								out.writeBytes(patient.lastname);								
								break;
							case MessageTypes.GET_OTHER_USERS:	
								int totalDocs = clientMap.size() - 1;
								out.writeInt(MessageTypes.OTHER_USERS); 
								System.out.println("Sending OTHER_USERS");
								out.writeInt(totalDocs);
								System.out.println("Sent size: "+ totalDocs);
								for(String id: clientMap.keySet()) {
									if (clientMap.get(id) != this)
									{
										System.out.print("Sending bytes: "+id.length());
										out.writeInt(id.length());
										System.out.println("Send:"+id);
										out.writeBytes(id);
									}
								}
								break;
							case MessageTypes.REFER_CASE:
								caseID = dis.readInt();
								int docid_len = dis.readInt();
								byte[] docID = new byte[docid_len];
								dis.readFully(docID);
								Case c = db.GetCaseByID(caseID);
								AddReferral(new String(docID), c);
								break;
							case MessageTypes.GET_REFERRALS:								
								docid_len = dis.readInt();
								docID = new byte[docid_len];
								out.writeInt(MessageTypes.REFERRAL_LIST);
								dis.readFully(docID);
								String docid_str = new String(docID);
								Vector<Case> referred_cases = GetReferrals(docid_str);
								out.writeInt(referred_cases.size());
								Vector<Integer> ids=new Vector<Integer>();
								for(Case rc:referred_cases) {
									out.writeInt(rc.id);
									RemoveReferral(new String(docID), rc.id);
								}
								break;
							case MessageTypes.GET_CASE_INFO:
								int caseid = dis.readInt();
								out.writeInt(MessageTypes.CASE_INFO);
							
								c = db.GetCaseByID(caseid);
								
								out.writeInt(c.id);
								out.writeInt(c.patientid);
								out.writeInt(c.name.length());
								out.writeBytes(c.name);
									
								break;
							default:
								out.writeInt(MessageTypes.ERROR);
								break;
						}
					} catch(SocketTimeoutException e) {
						// Don't do anything
						e.printStackTrace();
					} catch (NumberFormatException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}				
				
				RemoveClient(userID);
				
				dis.close();
				sis.close();
				
				out.close();
				sos.close();
				client.close();
				System.out.println("Client going down");
			} catch(IOException e) {
				e.printStackTrace();
			}
		}	
	}
	
	public PPServer(String _dbPath) {
		dbPath = _dbPath;
		quitFlag = false;
		clientList = new Vector<ClientHandler>();
		clientMap =new Hashtable<String, Thread>();
		referralMap = new HashMap<String, Vector<Case>>();
	}

	public void go()  {
		serverThread = new Thread(new Runnable() {			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("Starting up PPServer:42420");
				db = new PPDatabase(dbPath);
				db.OpenDB();
				try {
					mainSocket = new ServerSocket(42420);
					mainSocket.setSoTimeout(1000);
							
					while(!quitFlag ) {
						try {
							Socket client = null;
							client = mainSocket.accept();
							client.setSoTimeout(1000);
							
							System.out.println("Client conected");
							
							ClientHandler handler = new ClientHandler(client, db);
							clientList.add(handler);
							handler.start();
						} catch(SocketTimeoutException e) {
							//quitFlag = true;
							// Do nothing, just allow the main thread to check for user input and set the quit flag 
						}
					}
					
					mainSocket.close();
				} catch(IOException e) {
					e.printStackTrace();					
				}
			}			
		});		
		serverThread.start();
	}
	public void quit() {
		quitFlag = true;
		try {
			serverThread.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		for(ClientHandler handler:clientList) {
			handler.quit();
		}
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		//PPServer server= new PPServer("resource/db_big.csv");
		PPServer server= new PPServer("resource/db.csv");
		server.go();
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		boolean quit = false;
		while(!quit) {
			String s;
			try {
				s = br.readLine().toLowerCase();		
				if (s.equals("quit")) {
					server.quit();				
					quit = true;
				} else if (s.equals("unassign")) {
					System.out.println("Unassigning all cases");
					Vector<Integer> cases = server.db.GetCaseIDs();
					for(int i:cases) {
						server.db.CheckinCase(i);
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("PPServer exiting...");
	}
}
